#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include <fcntl.h>

#define MAXARGC  128
//命令行参数表
char* g_argv[MAXARGC];
int g_argc = 0;
//环境变量表
char* g_env[100];
int g_envs = 0;
//1、命令行提示符打印
#define FORMAT "[%s@%s %s]# "
#define COMMAND_SIZE 1024
//重定向操作需要关心的内容
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
int redir = NONE_REDIR;
std::string filename;
const char* GetHome()
{
    const char* home = getenv("HOME");
    return home == NULL ? "" : home;
}
const char* GetUsername()
{
    const char* username = getenv("USER");
    return username == NULL ? "None" : username;
}
const char* GetHostname()
{
    const char* hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}
char cwd[1024];
char cwdenv[1024];
const char* GetPwd()
{
    //const char* pwd = getenv("PWD");
    const char* pwd = getcwd(cwd , sizeof(cwd));
    if(pwd != NULL)
    {
        snprintf(cwdenv , sizeof(cwdenv) , "PWD=%s" , cwd);
        putenv(cwdenv);
    }
    return pwd == NULL ? "None" : pwd;
}
std::string GetDir(const char*  pwd)
{
    std :: string dir = pwd;
    if(dir == "/") return "/";
    size_t pos = dir.rfind("/");
    if(pos == std::string::npos) return "BUG?";
    return dir.substr(pos+1);
}
void MakeCmdPrompt(char* prompt , int size)
{
    //将字符串格式化写入prompt中
    snprintf(prompt , size , FORMAT , GetUsername() , GetHostname() ,GetDir(GetPwd()).c_str());
}
void PrintCmdPrompt()
{
    char cmd_prompt[COMMAND_SIZE];
    MakeCmdPrompt(cmd_prompt , COMMAND_SIZE);
    printf("%s" , cmd_prompt);
    fflush(stdout);
}
//2、获取命令行字符串
bool GetCommandLine(char* out , int size)
{
    char* c = fgets(out , size , stdin);
    if(c == NULL) return false;
    out[strlen(out)-1] = 0;//消除回车影响
    if(strlen(out) == 0) return false;
    return true;
}
//重定向操作
void TrimSpace(char cmd[], int& end)
{
	  while(1)
    {
        if(isspace(cmd[end]))
        {
            end++;
        }
        else break;
    }
}
void RedirCheck(char cmd[])
{
    filename.clear();
    redir = NONE_REDIR;
    int start = 0;
    int end = strlen(cmd) - 1;
    while(start < end)
    {
        if(cmd[end] == '<')
        {
            //输入重定向
            cmd[end++] = 0;
            TrimSpace(cmd, end);
            redir = INPUT_REDIR;
            filename = cmd + end;
            break;
        }
        else if(cmd[end] == '>')
        {
            //追加重定向
            if(cmd[end-1] == '>')
            {
                redir = APPEND_REDIR;
                cmd[end-1] = 0;
            }
            //输出重定向
            else 
            {
                redir = OUTPUT_REDIR;    
            }
            cmd[end++] = 0;
            TrimSpace(cmd, end);
            filename = cmd + end;
            break;
        }
        else 
        {
            end--;
        }
    }
}
//3、命令行解析
bool CommandParse(char* commandline , int size)
{
  #define SEP " "
    g_argc = 0;
    g_argv[g_argc++] = strtok(commandline , SEP);
    while((bool)(g_argv[g_argc++] = strtok(NULL , SEP)));
    g_argc--;
    return g_argc > 0 ? true : false; 
}

void PrintArgv()
{
    for(int i = 0 ; i < g_argc ; i++)
    {
      printf("argv[%d] -> %s\n" , i , g_argv[i]);
    }
}
//4、处理内建命令
int lastexit = 0;
bool Dealbuildin()
{
    std::string cwd = g_argv[0];
    if(cwd == "cd")
    {
        if(g_argc == 1)
        {
            //回到家目录
            std::string home = GetHome(); 
            if(home == "") return true;
            chdir(home.c_str());
        }
        else 
        {
          std::string where = g_argv[1];
          chdir(where.c_str());
        }
        return true;
    }
    else if(cwd == "echo")
    {
        std::string cont = g_argv[1];
        if(cont == "$?")
        {
            //获取上一次进程退出码，因为执行程序阶段已经记录，所以直接获得
            std::cout<<lastexit<<std::endl;
            lastexit = 0;//每次打印完就置为0
            return true;
        }
        if(cont[0] == '$')
        {
            //获取环境变量
            std::string str = cont.substr(1);//以string形式获取$之后的字符串 
            const char* e = getenv(str.c_str());
            std::cout<<e<<std::endl;
            return true;
        }
        else  
        {
            std::cout<<cont<<std::endl;
            return true;
        }
    }
    return false;
  }
//5、执行程序
void execute()
{
    pid_t id = fork();
    if(id == 0)
    {
      int fd = -1;
      //重定向操作
      if(redir == INPUT_REDIR)
      {
          fd = open(filename.c_str(), O_RDONLY);
          if(fd < 0) exit(1);
          dup2(fd, 0);
          close(fd);
      }
      else if(redir == OUTPUT_REDIR)
      {
          fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
          if(fd < 0) exit(1);
          dup2(fd, 1);
          close(fd);
      }
      else if(redir == APPEND_REDIR)
      {
          fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
          if(fd < 0) exit(1);
          dup2(fd, 1);
          close(fd);
      }
      else 
      {}
      execvp(g_argv[0] , g_argv);
    }
    int status = 0;
    pid_t rid = waitpid(id , &status , 0);
    if(rid > 0)
    {
        lastexit = WEXITSTATUS(status);
    }
}
//6、初始化环境变量表
void InitEnv()
{
    extern char** environ;
    g_envs = 0;
    memset(g_env , 0 , sizeof(g_env));

    //环境变量本来要从配置文件中来
    //获取环境变量
    for(int i = 0; environ[i]; i++)
    {
        g_env[i] = (char*)malloc(strlen(environ[i]) + 1);
        strcpy(g_env[i] , environ[i]);
        g_envs++;
    }
    //测试是不是我们初始化的表
    g_env[g_envs++] = (char*)"FOR_TEST";
    g_env[g_envs] = NULL;

    //导成环境变量
    for(int i = 0; i < g_envs; i++)
    {
        putenv(g_env[i]);
    }
    environ = g_env;
}
int main()
{
  InitEnv();
  while(1)
  {
    //1、打印命令行提示符
    PrintCmdPrompt();
    //2、获取命令行字符串
   char commandline[COMMAND_SIZE];
    if(!GetCommandLine(commandline , COMMAND_SIZE))
      continue;
    //printf("%s\n" , commandline);
    //3、重定向操作
    RedirCheck(commandline);
    //4、命令行解析
    if(!CommandParse(commandline , COMMAND_SIZE))
        continue;
   // PrintArgv();
   // 5、处理内建命令
   if( Dealbuildin() )
      continue;
   // 6、执行阶段
    execute();
  }
  return 0;
}
